#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
@File   : test02_disk_management.py
@Time : 2024/12/18 15:29
@Author: Wangwc
@Describe:  ……
"""
import random
import time
import allure
import pytest

from business.DiskApi import DiskApi
from commons.logger import log
from utils.login import Login
from commons.time_format import TimeFormat
from commons.fake_data import Fake

# TODO 当前模块的配置文件名称，配置在config目录下
config_name = 'cloud.ini'
login = Login(config_name=config_name)

# 在脚本开头添加全局变量
global_token = None

@pytest.fixture(scope="function")
def init_api():
    global global_token
    if global_token is None:
        global_token = login.login()
    api = DiskApi(global_token, configname=config_name)
    return api

def handle_api_call(api_method, *args, attach_to_allure=True, format_result=None, **kwargs):
    if not callable(api_method):
        raise TypeError("api_method 参数必须是可调用对象（如函数或方法）")
    if attach_to_allure is not None and not isinstance(attach_to_allure, bool):
        raise TypeError("attach_to_allure 参数必须是布尔类型或者为 None")
    if format_result is not None and not callable(format_result):
        raise TypeError("format_result 参数必须是可调用对象（如函数）或者为 None")

    max_retries = 3
    for retry in range(max_retries):
        try:
            result = api_method(*args, **kwargs)
            if attach_to_allure:
                if format_result:
                    formatted_result = format_result(result)
                else:
                    formatted_result = str(result)
                allure.attach(formatted_result, f"{api_method.__name__} 调用结果", allure.attachment_type.TEXT)
            log.info(f"{api_method.__name__} 调用成功")
            return result
        except Exception as e:
            allure.attach(str(e), f"{api_method.__name__} 调用错误信息", allure.attachment_type.TEXT)
            log.error(f"{api_method.__name__} 调用出现错误: {e}", exc_info=True)
            # 假设根据错误信息判断 token 过期
            if "token 过期" in str(e) and retry < max_retries - 1:
                global global_token
                global_token = login.login()
                api_method.__self__.token = global_token
                continue
            elif isinstance(e, ConnectionError):
                log.error("连接错误，可能是网络问题，请检查网络连接。")
            elif isinstance(e, ValueError):
                log.error("值错误，可能是传入的参数有误，请检查参数。")
            raise

@allure.feature("硬盘管理")
class TestDiskManagement():
    @allure.title("FIO读写测试")
    def test_fio_check(self, init_api):
        log.info(f"开始执行 FIO 读写测试用例{'-'*50}")
        with allure.step("获取未使用的硬盘名称"):
            disk_info = handle_api_call(init_api.get_unused_disk_name)
            disk_name = disk_info[0]

        with allure.step("确认没有在进行FIO读写性能检测"):
            if handle_api_call(init_api.get_fio_result, disk_name):
                log.info(f"当前硬盘 {disk_name} 正在进行 FIO 读写性能检测，手动停止FIO性能检测")
                handle_api_call(init_api.stop_fio_check, disk_name)
                time.sleep(1)
            assert handle_api_call(init_api.get_fio_result, disk_name) == None

        with allure.step("启动FIO读写性能检测"):
            x_curpass_token = login.get_curpass_token()
            handle_api_call(init_api.start_fio_check, disk_name, x_curpass_token=x_curpass_token)
            time.sleep(1)
            fio_result_data = handle_api_call(init_api.get_fio_result, disk_name)
            assert fio_result_data != None

        with allure.step("获取FIO检测结果"):
            start_time = time.time()
            timeout = 600  # 设置超时时间为 600 秒（可根据实际需求调整）
            while time.time() - start_time < timeout:
                if not handle_api_call(init_api.get_fio_result, disk_name):
                    break
                log.info(f"等待FIO读写性能检测完成，已等待 {time.time() - start_time} 秒")
                time.sleep(20)
            else:
                raise TimeoutError("在规定时间内未能获取到有效的 FIO 检测结果")
            detection_list = handle_api_call(init_api.get_disk_benchmark_info, disk_name)
            assert len(detection_list) > 0

    @allure.title("添加硬盘健康检测计划")
    def test_add_disk_cron(self, init_api):
        log.info(f"开始执行硬盘添加健康检测计划用例{'-' * 50}")
        with allure.step("获取健康检测计划列表"):
            disk_cron_list = handle_api_call(init_api.get_disk_cron_list)
            cron_num = len(disk_cron_list)
            log.info(f"当前健康检测计划数量为 {cron_num}")

        with allure.step("构造添加健康检测计划参数"):
            cron_name = Fake.fake_name()
            disks = handle_api_call(init_api.get_unused_disk_name) if handle_api_call(init_api.get_unused_disk_name) else handle_api_call(init_api.get_used_disk_name)
            log.info(f"待添加的健康检测计划名称为：{cron_name},检测的硬盘信息为：{disks}")

        with allure.step("添加硬盘健康检测计划"):
            first_run_datetime = TimeFormat.get_time_nday_later(5)
            handle_api_call(init_api.add_disk_cron_task, cron_name, disks,first_run_datetime)

        with allure.step("再次获取健康检测计划列表，确认成功添加"):
            disk_cron_list = handle_api_call(init_api.get_disk_cron_list)
            new_cron_num = len(disk_cron_list)
            assert new_cron_num == cron_num + 1, f"添加健康检测计划失败，当前健康检测计划数量为 {new_cron_num}"

    @allure.title("删除健康检测计划")
    def test_delete_disk_cron(self, init_api):
        log.info(f"开始执行删除健康检测计划测试用例{'-' * 50}")
        with allure.step("获取健康检测计划列表"):
            disk_cron_list = handle_api_call(init_api.get_disk_cron_list)
            cron_num = len(disk_cron_list)
            id = disk_cron_list[0].get('cron_id')
            log.info(f"当前健康检测计划数量为 {cron_num}")

        with allure.step("删除硬盘健康检测计划"):
            handle_api_call(init_api.delete_disk_cron_task, id)

        with allure.step("再次获取健康检测计划列表，确认成功添加"):
            time.sleep(1)
            disk_cron_list = handle_api_call(init_api.get_disk_cron_list)
            new_cron_num = len(disk_cron_list)
            assert new_cron_num == cron_num - 1, f"删除健康检测计划失败，当前健康检测计划数量为 {new_cron_num}，预期数量为：{cron_num -1}"


    @allure.title("硬盘定位")
    def test_disk_position(self, init_api):
        log.info(f"开始执行硬盘定位测试用例{'-'*50}")
        with allure.step("获取硬盘名称"):
            disk_list = handle_api_call(init_api.get_disk_list)
            index = random.randint(0, 3)
            dev_name = disk_list[index].get("dev_name")

        with allure.step("获取硬盘定位剩余时长"):
            result = handle_api_call(init_api.get_disk_position_endtime, dev_name)
            assert result == 0, f"硬盘 {dev_name} 在定位中，待定位完成后再重新开始定位"

        with allure.step("开始硬盘定位"):
            handle_api_call(init_api.start_disk_position, dev_name)
            time.sleep(3)
            result = handle_api_call(init_api.get_disk_position_endtime, dev_name)
            assert result != 0, f"硬盘 {dev_name} 定位失败，请重新开始定位"


        with allure.step("确认对应的硬盘灯颜色为绿色"):
            time.sleep(5)
            disk_name = f"disk{index+1}"
            disk_name_color = handle_api_call(init_api.get_disk_color, disk_name)
            assert "color:0:255:0" in disk_name_color, f"硬盘 {dev_name} 定位失败，请重新开始定位"

        with allure.step("还原硬盘定位"):
            handle_api_call(init_api.restore_disk_position, dev_name)
            result = handle_api_call(init_api.get_disk_position_endtime, dev_name)
            assert result == 0, f"硬盘 {dev_name} 还原定位失败，请重新还原定位"

    @allure.title("停用硬盘")
    def test_stop_use_disk(self, init_api):
        log.info(f"开始执行停用硬盘测试用例{'-' * 50}")
        with allure.step("使用shell脚本创建小容量的raid1"):
            pool_list = handle_api_call(init_api.create_raid1)
            pool_name = pool_list[0].get("name")
            log.info(f"获取到的初始存储池信息为: {pool_name}")

        with allure.step("确认存储池状态正常"):
            handle_api_call(init_api.check_pool_status)
            time.sleep(3)

        with allure.step("停用一块硬盘"):
            disk_name1 = handle_api_call(init_api.get_used_disk_name)[0]
            handle_api_call(init_api.stop_use_disk, disk_name1)

        with allure.step("确认硬盘成功停用"):
            time.sleep(3)
            activate_status = handle_api_call(init_api.get_disk_status, disk_name1, "activate")
            usage_status = handle_api_call(init_api.get_disk_status, disk_name1, "usage")
            assert activate_status == False, f"硬盘 {disk_name1} 未成功停用, activate状态为: {activate_status}"
            assert usage_status == 6, f"硬盘 {disk_name1} 未成功停用, usage状态为: {usage_status}"

    @allure.title("启用硬盘")
    def test_start_use_disk(self, init_api):
        log.info(f"开始执行启用硬盘测试用例{'-' * 50}")
        with allure.step("获取停用的硬盘名称"):
            disk_list = handle_api_call(init_api.get_disk_list)
            disk_name = None
            for disk in disk_list:
                if disk.get("activate") == False and disk.get("usage") == 6:
                    disk_name = disk.get("dev_name")
                    break
            assert disk_name is not None, f"未找到停用的硬盘"

        with allure.step("启用硬盘"):
            handle_api_call(init_api.start_use_disk, disk_name)

        with allure.step("确认硬盘成功启用"):
            time.sleep(3)
            activate_status = handle_api_call(init_api.get_disk_status, disk_name, "activate")
            usage_status = handle_api_call(init_api.get_disk_status, disk_name, "usage")
            assert activate_status == True, f"硬盘 {disk_name} 未成功启用, activate状态为: {activate_status}"
            assert usage_status == 0, f"硬盘 {disk_name} 未成功启用, usage状态为: {usage_status}"


    @allure.title("使用硬盘-修复存储池")
    def test_usedisk_fix_pool(self, init_api):
        log.info(f"开始执行修复存储池测试用例{'-'*50}")
        with allure.step("获取未使用的硬盘信息与存储池状态"):
            try:
                disk_name = handle_api_call(init_api.get_unused_disk_name)[0]
                pool_list = handle_api_call(init_api.get_pool_list)
                pool_status = pool_list[0].get("status")
                assert pool_status != 0, f"存储池状态为: {pool_status}，无需修复"
            except Exception as e:
                log.error(f"删除存储池时出现异常，异常信息: {e}")
                raise

        with allure.step("修复存储池"):
            try:
                pool_name = pool_list[0].get('name')
                handle_api_call(init_api.fix_pool, disk_name, pool_name)
                time.sleep(3)
                new_pool_list = handle_api_call(init_api.get_pool_list)
                new_pool_status = new_pool_list[0].get("status")
                assert new_pool_status == 8, f"修复存储池失败，存储池状态为: {new_pool_status}"
                log.info(f"修复存储池成功,存储池状态为: {new_pool_status}")
            except Exception as e:
                log.error(f"获取存储池信息时出现异常，异常信息: {e}")
                raise

        with allure.step("确认存储池修复完成"):
            try:
                handle_api_call(init_api.check_pool_status)
                time.sleep(5)
                log.info(f"存储池修复完成")
            except Exception as e:
                log.error(f"获取存储池信息时出现异常，异常信息: {e}")
                raise

        with allure.step("确认存储池修复后硬盘信息有变更"):
            disk_list = handle_api_call(init_api.get_unused_disk_name)
            assert disk_name not in disk_list, f"修复存储池后硬盘信息未变更，请重新修复存储池"
            log.info(f"修复存储池后硬盘信息有变更")

    @allure.title("使用硬盘-替换存储池硬盘")
    def test_create(self, init_api):
        log.info(f"开始执行使用硬盘-替换存储池硬盘测试用例{'-'*50}")
        with allure.step("获取替换前的硬盘信息"):
            try:
                used_disks1 = handle_api_call(init_api.get_used_disk_name)
                log.info(f"获取到的替换前的已使用的硬盘信息为: {used_disks1}")
                unused_disks = handle_api_call(init_api.get_unused_disk_name)
                log.info(f"获取到的替换前的未使用的硬盘信息为: {unused_disks}")
            except Exception as e:
                log.error(f"创建存储池时出现异常，异常信息: {e}")
                raise

        with allure.step("替换硬盘信息"):
            try:
                handle_api_call(init_api.check_pool_status)
                pool_list = handle_api_call(init_api.get_pool_list)
                pool_name = pool_list[0].get("name")
                replace_disk_list = handle_api_call(init_api.get_pool_operate_disk,pool_name=pool_name)
                replace_disk_name = replace_disk_list[0].get("dev_name")
                time.sleep(5)
                handle_api_call(init_api.replace_disk, pool_name,replace_disk_name,used_disks1[0])
            except Exception as e:
                log.error(f"替换硬盘时出现异常，异常信息: {e}")
                raise

        with allure.step("确认成功替换硬盘"):
            try:
                handle_api_call(init_api.check_pool_status)
                used_disks2 = handle_api_call(init_api.get_used_disk_name)
                assert used_disks1 != used_disks2, "替换硬盘失败"
            except Exception as e:
                log.error(f"替换硬盘时出现异常，异常信息: {e}")
                raise

    @allure.title("S.M.A.R.T.检测")
    def test_smart_check(self, init_api):
        log.info(f"开始执行 S.M.A.R.T.检测测试用例{'-'*50}")
        with allure.step("获取已使用的硬盘名称"):
            disk_info = handle_api_call(init_api.get_used_disk_name)
            disk_name = disk_info[0]

        with allure.step("启动S.M.A.R.T.检测"):
            handle_api_call(init_api.start_smart_check, disk_name)

        with allure.step("确认已经启动S.M.A.R.T.检测"):
            time.sleep(3)
            check_result = handle_api_call(init_api.get_smart_info, disk_name)
            assert check_result.get("is_testing") == True

        with allure.step("获取S.M.A.R.T.检测结果"):
            start_time = time.time()
            timeout = 600  # 设置超时时间为 600 秒（可根据实际需求调整）
            while time.time() - start_time < timeout:
                check_result = handle_api_call(init_api.get_smart_info, disk_name)
                if check_result.get("is_testing") == False:
                    break
                time.sleep(30)
            else:
                raise TimeoutError("在规定时间内未能获取到有效的 FIO 检测结果")
            result = handle_api_call(init_api.get_detection_list, disk_name)
            assert result[0].get('smart_type') == 0
            assert result[0].get('status') == 1

    @allure.title("IHM检测")
    def test_smart_check(self, init_api):
        log.info(f"开始执行HIM检测测试用例{'-' * 50}")
        with allure.step("获取检查前的IHM检查记录数"):
            disk_list = handle_api_call(init_api.get_disk_list)
            dev_name = None
            for disk in disk_list:
                if disk.get('model') == 'ST8000VN004-3CP101':
                    dev_name = disk.get('dev_name')
            result = handle_api_call(init_api.get_detection_list, dev_name)
            old_count = len(result)

        with allure.step("启动HIM检测"):
            handle_api_call(init_api.start_ihm_check,dev_name)

        with allure.step("确认已经启动HIM检测"):
            time.sleep(3)
            check_result = handle_api_call(init_api.get_ihm_info, dev_name)
            assert check_result.get("is_testing") == True

        with allure.step("获取HIM检测"):
            start_time = time.time()
            timeout = 600  # 设置超时时间为 600 秒（可根据实际需求调整）
            while time.time() - start_time < timeout:
                check_result = handle_api_call(init_api.get_ihm_info, dev_name)
                if check_result.get("is_testing") == False:
                    break
                time.sleep(30)
            else:
                raise TimeoutError("在规定时间内未能获取到有效的 FIO 检测结果")
            result = handle_api_call(init_api.get_detection_list, dev_name)
            new_count = len(result)
            assert new_count == old_count + 1 ,f"硬盘 {dev_name} 的IHM检测失败，请手工检测！"

    @allure.title("硬盘信息获取")
    def test_get_disk_info(self, init_api):
        log.info(f"开始执行硬盘信息获取测试用例{'-'*50}")
        with allure.step("获取已有硬盘列表"):
            disk_list = handle_api_call(init_api.get_disk_list)
            assert len(disk_list) == 6

        with allure.step("获取硬盘的详细信息-运行状况"):
            disk_info = handle_api_call(init_api.get_used_disk_name)
            disk_name = disk_info[0]
            status = handle_api_call(init_api.get_disk_runningState, disk_name)
            assert status == 1, f"硬盘 {disk_name} 的运行状况获取结果不符合预期，期望状态为 1，实际获取状态为 {status}"

        with allure.step("获取硬盘的详细信息-基本属性"):
            data = handle_api_call(init_api.get_disk_attribute, disk_name)
            assert data.get("brand") is not None, f"硬盘 {disk_name} 的基本属性中品牌信息获取为空，实际获取数据为 {data}"

        with allure.step("获取硬盘的详细信息-S.M.A.R.T.信息"):
            data = handle_api_call(init_api.get_disk_benchmark_info, disk_name)
            assert data.get("detection_list") is not None, f"硬盘 {disk_name} 的 S.M.A.R.T. 信息中检测列表获取为空，实际获取数据为 {data}"

    @allure.title("添加热备盘")
    def test_add_spare(self, init_api):
        log.info(f"开始执行添加热备盘测试用例{'-'*50}")
        with allure.step("获取存储池信息"):
            try:
                pool_list = handle_api_call(init_api.get_pool_list)
                raid_name = pool_list[0].get("pvs")
                spare = pool_list[0].get("spare")
            except Exception as e:
                log.error(f"获取存储池数量时出现异常，异常信息: {e}")
                raise

        with allure.step("获取添加热备盘前的硬盘信息"):
            try:
                unused_disks = handle_api_call(init_api.get_unused_disk_name)
                disk_name = unused_disks[0]
            except Exception as e:
                log.error(f"创建存储池时出现异常，异常信息: {e}")
                raise

        with allure.step("添加热备盘"):
            try:
                handle_api_call(init_api.creat_spare, raid_name, disk_name)
            except Exception as e:
                log.error(f"添加热备盘时出现异常，异常信息: {e}")
                raise

        with allure.step("确认成功添加热备盘"):
            try:
                pool_list = handle_api_call(init_api.get_pool_list)
                spare = pool_list[0].get("spare")
                assert spare[0].get("dev_name") == disk_name, "添加热备盘失败"
            except Exception as e:
                log.error(f"获取存储池数量时出现异常，异常信息: {e}")
                raise

        with allure.step("取消热备盘"):
            try:
                handle_api_call(init_api.stop_use_disk, disk_name)
                handle_api_call(init_api.start_use_disk, disk_name)
            except Exception as e:
                log.error(f"获取存储池数量时出现异常，异常信息: {e}")
                raise

    @allure.title("更换存储池RAID类型")
    def test_up_raid(self, init_api):
        log.info(f"开始执行更换存储池RAID类型测试用例{'-'*50}")
        with allure.step("获取存储池信息"):
            try:
                pool_list = handle_api_call(init_api.get_pool_list)
                pool_name = pool_list[0].get("name")
            except Exception as e:
                log.error(f"获取存储池数量时出现异常，异常信息: {e}")
                raise

        with allure.step("获取未使用的硬盘信息"):
            try:
                unused_disks = handle_api_call(init_api.get_unused_disk_name)
                disk_name = unused_disks[0]
            except Exception as e:
                log.error(f"创建存储池时出现异常，异常信息: {e}")
                raise

        with allure.step("升级RAID类型"):
            try:
                handle_api_call(init_api.up_raid, pool_name, disk_name)
            except Exception as e:
                log.error(f"替换硬盘时出现异常，异常信息: {e}")
                raise

        with allure.step("确认成功升级raid类型"):
            try:
                handle_api_call(init_api.check_pool_status)
                time.sleep(5)
                pool_list = handle_api_call(init_api.get_pool_list)
                raid_type = pool_list[0].get("level")
                assert raid_type == "raid5", "更换存储池RAID类型失败"
            except Exception as e:
                log.error(f"获取存储池数量时出现异常，异常信息: {e}")
                raise

    @allure.title("使用硬盘-添加到存储池")
    def test_ext_pool(self, init_api):
        log.info(f"开始执行使用硬盘-添加到存储池测试用例{'-'*50}")
        with allure.step("获取存储池信息"):
            try:
                pool_list = handle_api_call(init_api.get_pool_list)
                pool_name = pool_list[0].get("name")
            except Exception as e:
                log.error(f"获取存储池数量时出现异常，异常信息: {e}")
                raise

        with allure.step("获取未使用的硬盘信息"):
            try:
                unused_disks = handle_api_call(init_api.get_unused_disk_name)
                disk_name = unused_disks[0]
            except Exception as e:
                log.error(f"创建存储池时出现异常，异常信息: {e}")
                raise

        with allure.step("存储池扩容"):
            try:
                handle_api_call(init_api.check_pool_status)
                time.sleep(5)
                handle_api_call(init_api.extend_pool, pool_name, disk_name)
                log.info("已发送替换硬盘请求，等待替换完成")
            except Exception as e:
                log.error(f"替换硬盘时出现异常，异常信息: {e}")
                raise

        with allure.step("删除存储池"):
            try:
                handle_api_call(init_api.delete_pool_info)
            except Exception as e:
                log.error(f"替换硬盘时出现异常，异常信息: {e}")
                raise

    @allure.title("创建存储池")
    def test_create_pool(self, init_api):
        log.info(f"开始执行创建存储池测试用例{'-'*50}")
        with allure.step("先删除现有存储池"):
            try:
                handle_api_call(init_api.delete_pool_info)
            except Exception as e:
                log.error(f"删除存储池时出现异常，异常信息: {e}")
                raise

        with allure.step("获取存储池信息"):
            try:
                pool_info = handle_api_call(init_api.get_pool_name_list)
                pool_number1 = len(pool_info)
                assert pool_number1 == 0, f"当前存储池数量为 {pool_number1}，预期为0"
            except Exception as e:
                log.error(f"获取存储池信息时出现异常，异常信息: {e}")
                raise

        with allure.step("创建存储池"):
            try:
                handle_api_call(init_api.creat_pool)
            except Exception as e:
                log.error(f"创建存储池时出现异常，异常信息: {e}")
                raise

        with allure.step("确认存储池创建完成"):
            if handle_api_call(init_api.check_pool_status, status=1):
                log.info("存储池创建成功")
            else:
                raise TimeoutError("在规定时间内存储池数量未按预期增加，存储池创建可能失败")

        with allure.step("删除存储池，清理测试环境"):
            handle_api_call(init_api.delete_pool_info)
